package org.smartboot.compare.view;

import org.smartboot.compare.CompareResult;
import org.smartboot.compare.Option;
import org.smartboot.compare.difference.AbstractDifference;
import org.smartboot.compare.difference.Difference;
import org.smartboot.compare.difference.SizeDifference;

import java.util.HashMap;
import java.util.Map;

/**
 * @author qinluo
 * @date 2024-04-20 21:24:26
 * @since 1.0.9
 */
public class JsonResultViewer {

    public static final String RESULT = "result";
    public static final String DETAIL = "differenceDetails";


    /**
     * 比较结果
     */
    protected final CompareResult result;
    protected final Map<String, Object> viewModel;

    public JsonResultViewer(CompareResult result) {
        this.result = result;
        this.viewModel = new HashMap<>(32);
        this.genViewModel();
        this.adjustViewModel();
    }

    protected void genViewModel() {
        if (result == null) {
            return;
        }

        viewModel.put("id", result.getId());
        viewModel.put("maxDepth", result.getMaxDepth());
        viewModel.put("escaped", result.getEscaped());
        viewModel.put("recycleCnt", result.getRecycleCnt());
        viewModel.put(RESULT, result.isSame());
        viewModel.put("options", Option.serialize(result.getOptions()));
        viewModel.put("skippedFields", result.getSkippedFields());
        viewModel.put("messages", result.getMessages());
        viewModel.put("groupMessages", result.getGroupMessages());

        Map<String, Object> differences = new HashMap<>(16);
        viewModel.put(DETAIL, differences);
        boolean interrupted = Option.checkOption(result.getOptions(), Option.IMMEDIATELY_INTERRUPT);
        boolean ignoreSizeDifference = !interrupted && result.getDifferences(SizeDifference.class).size() < result.getDifferences().size();
        int index = 0;

        for (Difference difference : result.getDifferences()) {
            // 未指定IMMEDIATELY_INTERRUPT时优化SizeDifference属性
            if (difference instanceof SizeDifference && ignoreSizeDifference) {
                continue;
            }

            Map<String, Object> jsonViewModel = difference.getJsonViewModel();
            jsonViewModel = this.adjustJsonViewModel(difference, jsonViewModel);

            if (jsonViewModel == null) {
                jsonViewModel = getViewModel(difference);
            }

            if (difference instanceof AbstractDifference) {
                differences.put(((AbstractDifference) difference).getPath(), jsonViewModel);
            } else {
                differences.put(difference.type() + "-" + (index++), jsonViewModel);
            }

        }

        viewModel.put("differences", differences.size());
    }

    /**
     * Given subclass a chance to adjust global jsonViewModel.
     */
    protected void adjustViewModel() {

    }

    /**
     * Get object from viewModel with a string key.
     *
     * @param key key.
     * @param <S> strong cast java type.
     * @return    value.
     */
    public <S> S get(String key) {
        return (S)viewModel.get(key);
    }

    /**
     * Given subclass a chance to adjust jsonViewModel.
     *
     * @param difference    difference
     * @param jsonViewModel difference view's model.
     * @return              after adjust model.
     */
    protected Map<String, Object> adjustJsonViewModel(Difference difference, Map<String, Object> jsonViewModel) {
        return jsonViewModel;
    }

    /**
     * Generate jsonViewModel with given difference.
     *
     * @param difference given difference.
     * @return           model.
     */
    protected Map<String, Object> getViewModel(Difference difference) {
        Map<String, Object> details = new HashMap<>();
        details.put("message", difference.getMessage());
        return details;
    }

    /**
     * 自定义Json序列化
     */
    public String toJson(JsonSerializer serializer) {
        return serializer.toJson(viewModel);
    }

    @Override
    public String toString() {
        // Init default serializer.
        initDefaultSerializerIfNecessary();
        JsonSerializer instance = JsonSerializer.getInstance();
        if (instance == null) {
            throw new IllegalStateException("Please init JsonSerializer");
        }

        return toJson(JsonSerializer.getInstance());
    }

    private void initDefaultSerializerIfNecessary() {
        JsonSerializer instance = JsonSerializer.getInstance();
        if (instance != null) {
            return;
        }

        // fastjson2 >> fastjson > gson.
        if (isPresent("com.alibaba.fastjson2.JSON")) {
            JsonSerializer.setDefaultInstance(new FastJson2Serializer());
        } else if (isPresent("com.alibaba.fastjson.JSON")) {
            JsonSerializer.setDefaultInstance(new FastJsonSerializer());
        } else if (isPresent("com.google.gson.Gson")) {
            JsonSerializer.setDefaultInstance(new GsonSerializer());
        }
    }

    private static boolean isPresent(String classname){
        try {
            Class.forName(classname);
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        }
    }
}
